package com.makemoney.miniprogram.modules.users.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.makemoney.miniprogram.common.consts.RedisKey;
import com.makemoney.miniprogram.common.result.BusinessException;
import com.makemoney.miniprogram.common.result.ResultCode;
import com.makemoney.miniprogram.common.util.IRedisService;
import com.makemoney.miniprogram.modules.users.dao.IRoleResourceDao;
import com.makemoney.miniprogram.modules.users.dao.IUserRoleDao;
import com.makemoney.miniprogram.modules.users.dao.IUsersDao;
import com.makemoney.miniprogram.modules.users.dto.IMyUserDetails;
import com.makemoney.miniprogram.modules.users.dto.LoginDTO;
import com.makemoney.miniprogram.modules.users.dto.MyUserDetails;
import com.makemoney.miniprogram.modules.users.dto.TokenDTO;
import com.makemoney.miniprogram.modules.users.entity.Resources;
import com.makemoney.miniprogram.modules.users.entity.Roles;
import com.makemoney.miniprogram.modules.users.entity.Users;
import com.makemoney.miniprogram.modules.users.service.IAuthorizeService;
import com.makemoney.miniprogram.security.util.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@Service
public class AuthorizeServiceImpl implements IAuthorizeService {
    // JWT 令牌请求头（即：Authorization）
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    // JWT 令牌前缀（即：Bearer）
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Resource
    private IUsersDao usersDao;
    @Resource
    private IUserRoleDao userRoleDao;
    @Resource
    private IRoleResourceDao roleResourceDao;
    @Resource
    private BCryptPasswordEncoder passwordEncoder;
    @Resource
    private JwtTokenUtil jwtTokenUtil;
    @Resource
    private IRedisService redisService;

    @Override
    public TokenDTO login(LoginDTO loginDTO) {
        IMyUserDetails user = this.loadUserByUsername(loginDTO.getUserName());
        if (ObjectUtil.isNull(user) || !passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
            throw new BusinessException(ResultCode.A0220);
        }
        usersDao.updateLoginTime(user.getUser().getId());
        TokenDTO dto = new TokenDTO();
        dto.setToken(jwtTokenUtil.generateToken(user.getUsername()));
        dto.setTokenHead(jwtTokenUtil.getTokenHead());
        dto.setExpirationDate(DateUtil.toLocalDateTime(jwtTokenUtil.getExpiredDateFromToken(dto.getToken())));
        return dto;
    }

    @Override
    public boolean logout(IMyUserDetails userDetails) {
        if (ObjectUtil.isNotNull(userDetails)) {
            String redisKey = RedisKey.USER + StrUtil.COLON + userDetails.getUsername();
            redisService.del(redisKey);
        }
        //redisService
        return Boolean.TRUE;
    }

    @Override
    public TokenDTO refreshToken(HttpServletRequest request) {
        String authHeader = request.getHeader(this.tokenHeader);
        String authToken = authHeader.substring(this.tokenHead.length());
        String refreshHeadToken = jwtTokenUtil.refreshHeadToken(authToken);
        TokenDTO dto = new TokenDTO();
        dto.setTokenHead(this.tokenHead.trim());
        dto.setToken(refreshHeadToken);
        dto.setExpirationDate(DateUtil.toLocalDateTime(jwtTokenUtil.getExpiredDateFromToken(refreshHeadToken)));
        return dto;
    }

    @Override
    public IMyUserDetails loadUserByUsername(String username) {
        String redisKey = RedisKey.USER + StrUtil.COLON + username;
        if (redisService.isEnabled() && redisService.hasKey(redisKey)) {
            return (MyUserDetails) redisService.get(redisKey);
        }
        Users user = usersDao.findUserByUsername(username);
        if (ObjectUtil.isNull(user)) {
            throw new BusinessException(ResultCode.A0220);
        }
        List<Roles> roles = userRoleDao.listByUserId(user.getId());
        MyUserDetails userDetails = new MyUserDetails(user, roles);
        if (redisService.isEnabled()) {
            redisService.set(redisKey, userDetails);
        }
        return userDetails;
    }

    @Override
    public boolean hasPermission(UserDetails userDetails, String url) {
        if (userDetails.getAuthorities().stream().anyMatch(a -> "ADMIN".equals(a.getAuthority()))) {
            return Boolean.TRUE;
        }

        for (GrantedAuthority authority : userDetails.getAuthorities()) {
            List<Resources> resources = roleResourceDao.listByRoleKey(authority.getAuthority());
            if (resources.stream().anyMatch(r -> r.getResourceUrl().equals(url))) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }
}
